#include <iostream>
#include <sstream>
#include <cstring>
#include <cstdlib>
#include <fstream>

#include "cm_net.h"
#include "dispatcher.h"
#include "vfile.h"
#include <stdio.h>
using std::string;
using std::cout;
using std::vector;

class Server: public Dispatcher {
	UdpServer udp_server;
	typedef std::map<string, Vfile>::iterator iterator;
	std::map<string, Vfile> archive;
	TcpListener listener;
public:
	Server(string rep_location) :
			Dispatcher("<clientname>"), listener(SERVER_TCP_PORT) {
		Vfile::path = rep_location;
		listener.listen();
	}

	virtual cmd_func add_file;
	virtual cmd_func checkout;
	virtual cmd_func checkin;
	virtual cmd_func get;
	virtual cmd_func view_files;
	virtual cmd_func view_versions;
	virtual cmd_func update_owner;
	virtual cmd_func undo_checkout;
	virtual cmd_func view_my_co;
	virtual cmd_func quit;

	Vfile& get_vfile(string name) {
		std::cout << "get versions()\n";
		std::map<string, Vfile>::iterator iter = this->archive.find(name);
		if (iter == this->archive.end())
			throw IllegalAction(Error_FILE_DOES_NOT_EXIST);
		return iter->second;
	}

	Vfile& get_vfile_exclusively(string name) {
		Vfile& f = get_vfile(name);
		if (f.checked_out())
			throw IllegalAction(Error_FILE_LOCKED);
		return f;
	}

	virtual string readline() {
		cout << "<-- ready and waiting -->\n";
		return udp_server.receive();
	}

	virtual void handle(Error error) {
		char str[2] = { static_cast<char>(error), '\0' };
		std::cout << "Error: " << error << "\n";
		send_back_udp(str);
	}

	TcpChannel get_channel() {
		return listener.accept();
	}

	void send_ok() {
		send_back_udp(string("", 1));
	}

	void tcp_receive(std::ostream& os) {
		send_ok();
		get_channel().read_into(os);
	}

	void tcp_send(std::istream& is) {
		send_ok();
		get_channel().write_from(is);
	}

	void send_back_udp(string s) {
		udp_server.last_addr_p->send(s);
	}

	void safe_send_back_udp(string s) {
		send_ok();
		SafeUdp(*udp_server.last_addr_p).send(s);
	}
};

int main(int argc, char *argv[]) {
	if (argc != 2) {
		cout << ("usage: cmserver <repository_location>");
		return 1;
	}
	cout << "Server is running...\n";
	string repo_loc = argv[1];
	try {
		Server(repo_loc).run();
	} catch (ConnectionError& c) {
		std::cout << c.what << "\n";
		throw;
	}
}

void Server::add_file(Map<string, string> args) {
	string filename = args["filename"];
	if (archive.count(filename)) {
		throw IllegalAction(Error_FILE_EXISTS);
	}
	Vfile newfile(filename, args["clientname"]);
	archive.insert(archive.find(filename),
			std::pair<string, Vfile>(filename, newfile));
	string new_file_path = newfile.add_version_to_path(Version(args["clientname"],""));
	std::ofstream ofs(new_file_path.c_str(),std::ofstream::out);
	tcp_receive(ofs);
	ofs.close();
	newfile.unlock_user();
}

void Server::checkin(Map<string, string> args) {
	Vfile& f = get_vfile(args["filename"]);
	if (!f.checked_out_by(args["clientname"])){
		throw IllegalAction(Error_NOT_CHECKED_OUT, args["clientname"]);
	}
	string new_file_path = f.add_version_to_path(Version(args["clientname"],args["comment"]));
	std::ofstream ofs(new_file_path.c_str(),std::ofstream::out);
	tcp_receive(cout);
	ofs.close();
	f.unlock_user();
}

void Server::checkout(Map<string, string> args) {
	//critical section - begin
	Vfile& f = get_vfile_exclusively(args["filename"]);
	string filepath = f.get_last_version_path();
	std::ifstream ifs(filepath.c_str(),std::ifstream::in);
	tcp_send(ifs);
	f.checkout(args["clientname"]);
	//critical section - end
}

static int string_to_int(string& str) {
	int res;
	sscanf(str.c_str(), "%d", &res);
	return res;
}

void Server::get(Map<string, string> args) {
	Vfile& f = get_vfile(args["filename"]);
	string last_version = f.get_last_version();
	args.insert(args.find("version"), std::pair<string, string>("version",last_version));
	int version = string_to_int(args["version"]);
	if (version>string_to_int(last_version))
		throw IllegalAction(Error_NO_SUCH_VERSION);
	string filepath = f.get_version_path(version);
	std::ifstream ifs(filepath.c_str(),std::ifstream::in);
	tcp_send(ifs);
	ifs.close();
}

void Server::view_files(Map<string, string> args) {
	cout << "view_files\n";
	std::stringstream ss;
	for (iterator pf = archive.begin(); pf != archive.end(); ++pf) {
		ss << pf->first << ": " << pf->second.get_description() << "\n";
	}
	safe_send_back_udp(ss.str());
}

void Server::view_versions(Map<string, string> args) {
	cout << "view_versions\n";
	safe_send_back_udp(get_vfile(args["filename"]).get_versions());
}

//update_owner + david file1.c
void Server::update_owner(Map<string, string> args) {
	cout << "update_owner\n";
	Vfile& f = get_vfile_exclusively(args["filename"]);
	if (!f.is_owner(args["clientname"]))
	throw IllegalAction(Error_NOT_AN_OWNER);

	string name = args["username"];
	switch (args["+|-"][0]) {
		case '+' : f.add_owner(name); break;
		case '-' : f.remove_owner(name); break;
	}
}

void Server::undo_checkout(Map<string, string> args) {
	cout << "undo_checkout\n";

	get_vfile_exclusively(args["filename"]).undo_checkout(args["clientname"]);
	//send_ok();
}

void Server::view_my_co(Map<string, string> args) {
	cout << "view_my_co\n";

	std::stringstream ss;
	for (iterator pf = archive.begin(); pf != archive.end(); ++pf)
	if (pf->second.checked_out())
	ss << pf->first << ", ";
	ss.unget().unget();
	safe_send_back_udp(ss.str());
}

void Server::quit(Map<string, string> args) {
	::exit(0);
}
